﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Windy.TurtleDrawing;
using Windy.Transformations;
using System.Diagnostics.Contracts;
using System.Windows.Media;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Windows;
using NLog;

namespace Windy.DragonCurve
{
    public partial class DragonCurve : INotifyPropertyChanged
    {
        private static Logger logger = NLog.LogManager.GetCurrentClassLogger();

        public DragonCurveScheme Scheme { get; private set; }
        public bool IsInitialized { get { return Scheme != null; } }

        public Transformation<Symbols, LinkedListNode<Symbols>, Symbols> CurveRules { get; private set; }
        public Transformation<Symbols, Symbols, Action<DragonCurve, Turtle>> PlotRules { get; private set; }
        public double StepAngle { get; private set; }

        public DragonCurve()
        {
            symbolEnumerator = new DragonCurveSymbolEnumerator(this);
            StepLength = 10;
        }

        public DragonCurve(DragonCurveScheme scheme)
            : this()
        {
            ConfigureCurve(scheme);
        }

        public void ConfigureCurve(DragonCurveScheme scheme)
        {
            Scheme = scheme;

            if (Scheme != null)
            {
                logger.Info("Loading Scheme \"{0}\"", Scheme.Name);

                if (!scheme.IsValid)
                {
                    throw new ArgumentException("scheme is not valid", "scheme");
                }

                CurveRules = new Transformation<Symbols, LinkedListNode<Symbols>, Symbols>
                    ((li) => li.Value, scheme.CurveRules);

                PlotRules = new Transformation<Symbols, Symbols, Action<DragonCurve, Turtle>>
                    ((s) => s, scheme.PlotRules);

                this.StepAngle = scheme.StepAngel.Value;

                UpdateSymbols(scheme.InitialSymbols);

                logger.Info("Load Scheme \"{0}\" Succeeded", Scheme.Name);
            }
            else
            {
                logger.Info("Scheme is Cleared");
            }
        }
        public void SetSymbols(params Symbols[] symbols)
        {
            Contract.Requires<ArgumentNullException>(symbols != null, "symbols is null");

            UpdateSymbols(symbols);
        }
        public void SetSymbols(IEnumerable<Symbols> symbols)
        {
            Contract.Requires<ArgumentNullException>(symbols != null, "symbols is null");

            UpdateSymbols(symbols);
        }

        protected void UpdateSymbols(IEnumerable<Symbols> list)
        {
            Contract.Requires(list != null);

            symbols = new LinkedList<Symbols>(list);
            NotifySymbolChanged();
        }

        protected LinkedList<Symbols> symbols;
        public IEnumerable<Symbols> Symbols { get { return symbols; } }

        protected DragonCurveSymbolEnumerator symbolEnumerator;

        public void Update()
        {
            logger.Info("Begin Update Dragon Curve");
            if (!IsInitialized)
            {
                logger.Warn("Dragon Curve is not initialized");
                return;
            }

            logger.Debug("Symbols Before: {0}", Symbols.ToSymbolString());
            using (var sink = new CachedTransformSink<LinkedListNode<Symbols>, Symbols>())
            {
                sink.SetupVoidTransformSink(
                        (index, input) =>
                        {
                            logger.Info("{0} is removed", input.Value.ToSymbolString());
                            symbols.Remove(input);
                        })
                    .SetupMonoTransformSink(
                        (index, input, output) =>
                        {
                            if (input.Value != output)
                            {
                                logger.Trace("{0} is replaced by {1}", input.Value.ToSymbolString(), output.ToSymbolString());
                                symbols.AddAfter(input, output);
                                symbols.Remove(input);
                            }
                            else
                            {
                                logger.Trace("{0} is not changed", input.Value.ToSymbolString());
                            }

                        })
                    .SetupVaryTransformSink(
                        (index, input, output, addAction) =>
                        {
                            logger.Info("{0} is replaced by {1}", input.Value.ToSymbolString(), output.ToSymbolString());
                            foreach (var fo in output)
                            {
                                /* 
                                 * Make fo as a local copy for further used in lambda expression,
                                 * which enforce the generated anonymous class to keep a value copy
                                 * of the fo rather than a reference.
                                 * Or else the value of foCopy is changing all the time.
                                 */
                                Symbols foCopy = fo; // Magic Code, Do not remove!
                                addAction(() => symbols.AddBefore(input, foCopy));
                            }
                            addAction(() => symbols.Remove(input));
                        });

                CurveRules.ApplyTo(symbolEnumerator, sink);
            }
            logger.Debug("Symbols After: {0}", Symbols.ToSymbolString());

            logger.Info("End Update Dragon Curve");
            NotifySymbolChanged();
        }

        public void Reset()
        {
            logger.Info("Reset Dragon Curve");
            if (!IsInitialized)
            {
                logger.Warn("Dragon Curve is not initialized");
                return;
            }

            UpdateSymbols(Scheme.InitialSymbols);
        }
        public void Refresh()
        {
            this.geometry = null;
            this.path = null;
            Notify("Geometry");
            Notify("Path");
        }

        protected IEnumerable<Action<DragonCurve, Turtle>> BuildDrawActions()
        {
            if (!IsInitialized)
                throw new InvalidOperationException();

            return PlotRules.ApplyTo(symbols);
        }

        #region Notify Property StepLength
        private double stepLength;
        public double StepLength
        {
            get { return stepLength; }
            set
            {
                if (stepLength == value)
                    return;

                stepLength = value;
                Notify("StepLength");
            }
        }
        #endregion

        #region Notify Property StartPoint
        private Point startPoint = new Point(50,50);
        public Point StartPoint
        {
            get { return startPoint; }
            set
            {
                if (startPoint == value)
                    return;

                startPoint = value;
                Notify("StartPoint");
            }
        }
        #endregion


        private StreamGeometry geometry;
        public Geometry Geometry
        {
            get
            {
                if (!IsInitialized)
                {
                    logger.Info("Geometry is not fetched: Dragon Curve is not initialized.");
                    return null;
                }

                if (geometry == null)
                {
                    logger.Info("Create new Geometry");

                    logger.Trace("Create StreamGeometry");
                    geometry = new StreamGeometry();


                    using (var turtle = geometry.CreateTurtle(StartPoint))
                    {
                        foreach (var foAction in BuildDrawActions())
                        {
                            logger.Trace("Draw Action");
                            foAction(this, turtle);
                        }
                    }

                    logger.Trace("Freeze Geometry");
                    geometry.Freeze();
                }

                logger.Info("Geometry is fetched.");
                return geometry;
            }
        }

        private Path path;
        public Path Path
        {
            get
            {
                if (!IsInitialized)
                    return null;

                if (path == null)
                {
                    path = new Path();
                    path.Data = Geometry;
                }

                return path;
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        protected void Notify(string propertyName)
        {
            logger.Info("Property {0} changed", propertyName);

            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        protected void NotifySymbolChanged()
        {
            logger.Info("Symbols Updated: {0}", Symbols.ToSymbolString());

            symbolEnumerator.Reset();
            Refresh();
        }

        #endregion
    }
}